package storage

import (
	"bytes"
	"encoding/gob"
	"github.com/Unixeno/TheMoon/common/zone"
	"github.com/Unixeno/TheMoon/logging"
	"github.com/Unixeno/TheMoon/pool"
	"github.com/boltdb/bolt"
)

var localDB *bolt.DB
var ModuleName = "storage"

var networkBucket = []byte("ipam")
var zoneBucket = []byte("zone")
var collectErrorBucket = []byte("collectError")
var deployerBucket = []byte("deployer")

var logger = logging.GetLogger(ModuleName)

type LocalZoneInfo struct {
	Rules   string
	Version int32 // 本地version统一使用int32
	State   zone.ZoneMoonState
}

type LocalDeployer struct {
	ZoneID  string
	Version int32

	Containers map[string]string
	Volumes    map[string]string
	Networks   map[string]string
}

type LocalCollectError struct {
	Category int
	ZoneID   string
}

func InitLocalStorage() {
	logger.Debug("init local storage")
	db, err := bolt.Open("moon.db", 0600, nil)
	if err != nil {
		logger.Fatal("failed to open local storage", err)
		return
	}
	localDB = db

	_ = db.Update(func(tx *bolt.Tx) error {
		_, _ = tx.CreateBucketIfNotExists(zoneBucket)
		_, _ = tx.CreateBucketIfNotExists(networkBucket)
		_, _ = tx.CreateBucketIfNotExists(deployerBucket)
		return nil
	})
}

func ListZone() (list map[string]LocalZoneInfo) {
	list = make(map[string]LocalZoneInfo)
	_ = localDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(zoneBucket)
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			dec := gob.NewDecoder(bytes.NewBuffer(v))
			item := LocalZoneInfo{}
			dec.Decode(&item)
			list[string(k)] = item
		}
		return nil
	})
	return
}

func GetZone(zoneID string) (info LocalZoneInfo, ok bool) {
	_ = localDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(zoneBucket)
		v := b.Get([]byte(zoneID))
		if v == nil {
			ok = false
		} else {
			ok = true
			dec := gob.NewDecoder(bytes.NewBuffer(v))
			_ = dec.Decode(&info)
		}
		return nil
	})
	return
}

func PutZone(zoneID string, info LocalZoneInfo) {
	logger.WithField("zoneID", zoneID).WithField("v", info.Version).WithField("s", info.State).
		Debug("put zone")
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(zoneBucket)
		buf := pool.Fixed64KPool.Get().([]byte)
		bytesBuf := bytes.NewBuffer(buf)
		bytesBuf.Reset()
		enc := gob.NewEncoder(bytesBuf)
		_ = enc.Encode(info)
		_ = b.Put([]byte(zoneID), bytesBuf.Bytes())
		pool.Fixed64KPool.Put(buf)
		return nil
	})
	return
}

func UpdateZoneState(zoneID string, state zone.ZoneMoonState) {
	logger.WithField("zoneID", zoneID).WithField("s", state).Debug("update state")
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(zoneBucket)

		info := LocalZoneInfo{}
		v := b.Get([]byte(zoneID))
		if v == nil {
			return nil
		}
		dec := gob.NewDecoder(bytes.NewBuffer(v))
		_ = dec.Decode(&info)

		info.State = state

		buf := pool.Fixed64KPool.Get().([]byte)
		bytesBuf := bytes.NewBuffer(buf)
		bytesBuf.Reset()
		enc := gob.NewEncoder(bytesBuf)
		_ = enc.Encode(info)
		_ = b.Put([]byte(zoneID), bytesBuf.Bytes())
		pool.Fixed64KPool.Put(buf)
		return nil
	})
}

func DumpNetwork(data []byte) error {
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(networkBucket)
		_ = b.Put(networkBucket, data)
		return nil
	})
	return nil
}

func LoadNetwork() (val []byte, ok bool) {
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(networkBucket)
		val = b.Get(networkBucket)
		ok = (val != nil)
		return nil
	})
	return
}

func PutCollectError(id string, info LocalCollectError) {
	logger.WithField("garbage id", id).Debug("put collect error")
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(collectErrorBucket)
		buf := pool.Fixed64KPool.Get().([]byte)
		bytesBuf := bytes.NewBuffer(buf)
		bytesBuf.Reset()
		enc := gob.NewEncoder(bytesBuf)
		_ = enc.Encode(info)
		_ = b.Put([]byte(id), bytesBuf.Bytes())
		pool.Fixed64KPool.Put(buf)
		return nil
	})
	return
}

func ListCollectError() (list map[string]LocalCollectError) {
	list = make(map[string]LocalCollectError)
	_ = localDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(collectErrorBucket)
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			dec := gob.NewDecoder(bytes.NewBuffer(v))
			item := LocalCollectError{}
			dec.Decode(&item)
			list[string(k)] = item
		}
		return nil
	})
	return
}

func DeleteCollectError(id string) {
	logger.WithField("garbage id", id).Debug("delete collect error")
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(collectErrorBucket)
		b.Delete([]byte(id))
		return nil
	})
	return
}

func PutLocalDeployer(zoneID string, info LocalDeployer) {
	logger.WithField("zoneID", zoneID).Debug("put local deployer")
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(deployerBucket)
		buf := pool.Fixed64KPool.Get().([]byte)
		bytesBuf := bytes.NewBuffer(buf)
		bytesBuf.Reset()
		enc := gob.NewEncoder(bytesBuf)
		_ = enc.Encode(info)
		_ = b.Put([]byte(zoneID), bytesBuf.Bytes())
		pool.Fixed64KPool.Put(buf)
		return nil
	})
	return
}

func DeleteLocalDeployer(zoneID string) {
	logger.WithField("zoneID", zoneID).Debug("delete deploy error")
	_ = localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket(deployerBucket)
		b.Delete([]byte(zoneID))
		return nil
	})
	return
}

func ListLocalDeployer() (list map[string]LocalDeployer) {
	list = make(map[string]LocalDeployer)
	_ = localDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(deployerBucket)
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			dec := gob.NewDecoder(bytes.NewBuffer(v))
			item := LocalDeployer{}
			dec.Decode(&item)
			list[string(k)] = item
		}
		return nil
	})
	return
}

func CreateBucket(bucket string) {
	_ = localDB.Update(func(tx *bolt.Tx) error {
		_, _ = tx.CreateBucketIfNotExists([]byte(bucket))
		return nil
	})
}

func Put(bucket string, key []byte, value []byte) error {
	return localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		return b.Put(key, value)
	})
}

func Delete(bucket string, key []byte) error {
	return localDB.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		return b.Delete(key)
	})
}

func Get(bucket string, key []byte) (value []byte, err error) {
	err = localDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		value = b.Get(key)
		return nil
	})
	return value, err
}

// 获取bucket内第一个元素，当bucket为空时，key为nil
func GetOne(bucket string) (key []byte, value []byte, err error) {
	err = localDB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucket))
		if b != nil {
			key, value = b.Cursor().First()
		}
		return nil
	})
	return key, value, err
}
